﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HCM_maps_website.Process.DAL;
using HCM_maps_website.Models;

namespace HCM_maps_website.Process.BLL
{
    public class DirectionService
    {
        private static ListNodeInfo nodeInfoList=new ListNodeInfo();

        public static ListNodeInfo NodeInfoList
        {
            get { return nodeInfoList; }
            set { nodeInfoList = value; }
        }

        private static ListSegmentInfo segmentInfoList=new ListSegmentInfo();

        public static ListSegmentInfo SegmentInfoList
        {
            get { return segmentInfoList; }
            set { segmentInfoList = value; }
        }


        public static List<GraphNodeInfoEx> ShortestPath(NodeInfo_old fromNode, NodeInfo_old toNode)
        {
            Initializer();

            GraphNodeServiceEx gnodeServiceEx = new GraphNodeServiceEx();

            GraphNodeInfoEx from = new GraphNodeInfoEx();
            GraphNodeInfoEx end = new GraphNodeInfoEx();

            from.Id = fromNode.Id;
            from.Location = fromNode.Location;
            end.Id = toNode.Id;
            end.Location = toNode.Location;

            from.Father = null;

            List<GraphNodeInfoEx> openList = new List<GraphNodeInfoEx>();
            List<GraphNodeInfoEx> closedList = new List<GraphNodeInfoEx>();

            openList.Add(from);
            int count = 0;
            while (openList.Count > 0 && count < 1000)
            {
                count++;
                
                GraphNodeInfoEx currentNode = new GraphNodeInfoEx(openList[0]);
                
                closedList.Add(currentNode);
                openList.RemoveAt(0);

                if (currentNode.Equals(end))
                {
                    return GetShortestPath(closedList);
                }

                List<GraphNodeInfoEx> isRelataionList = gnodeServiceEx.GetListNodeRelate(currentNode.Location);
                for (int i = 0; i < isRelataionList.Count; i++)
                {
                    if (openList.Contains(isRelataionList[i]) && closedList.Contains(isRelataionList[i]))
                        isRelataionList.RemoveAt(i);
                }

                foreach (var node in isRelataionList)
                {
                    if (node.Equals(end))
                    {
                        closedList.Add(node);
                        return GetShortestPath(closedList);
                    }

                    node.CalculateHeuristic(end);
                    node.G = currentNode.G + (float)Coordinate.Distance(currentNode.Location, node.Location);
                    node.F = node.H + node.G;
                    node.Father = currentNode;

                }
                
                isRelataionList.Sort();

                if (isRelataionList.Count >= 3)
                {
                    openList.Add(isRelataionList[0]);
                    openList.Add(isRelataionList[1]);
                    //openList.Add(isRelataionList[2]);
                }
                else
                {
                    foreach (GraphNodeInfoEx node in isRelataionList)
                    {
                        openList.Add(node);
                    }
                }

                openList.Sort();

            }

            return null;
        }

        public static void Initializer()
        {
            nodeInfoList = DirectionDAO.GetAllNodeInfo();
            segmentInfoList = DirectionDAO.GetAllSegmentInfo();

            //throw new NotImplementedException();
        }
        private static List<GraphNodeInfoEx> GetNodeRelation(int nodeID)
        {
            List<GraphNodeInfoEx> resultList = new List<GraphNodeInfoEx>();

            
            for (int i = 0; i < segmentInfoList.SegmentInfoList.Count; i++ )
            {
                GraphNodeInfoEx gn = new GraphNodeInfoEx();
                SegmentInfo segment = segmentInfoList.SegmentInfoList[i];
                if (segment.StartPoint.Id == nodeID)
                {
                    gn.Id = segment.EndPoint.Id;
                    SetLocation(resultList, gn);
                }

                if (segment.EndPoint.Id == nodeID && segment.Twoway == 1)
                {
                    gn.Id = segment.StartPoint.Id;
                    SetLocation(resultList, gn);
                }

            }
           
            return resultList;

        }

        private static void SetLocation(List<GraphNodeInfoEx> resultList, GraphNodeInfoEx gn)
        {
            for (int i = 0; i < nodeInfoList.NodeInfoList.Count; i++)
            {
                if (nodeInfoList.NodeInfoList[i].Id == gn.Id)
                {
                    gn.Location = nodeInfoList.NodeInfoList[i].Location;
                    resultList.Add(gn);
                    break;
                }
            }
        }
        private static bool IsStartEndSegment(Coordinate coor)
        {

            for (int i = 0; i < nodeInfoList.NodeInfoList.Count; i++)
            {
                NodeInfo_old node = nodeInfoList.NodeInfoList[i];
                if (coor.Equals(node.Location))
                {
                    foreach (var item in segmentInfoList.SegmentInfoList)
                    {
                        if (Convert.ToInt32(item.StartPoint) == node.Id || Convert.ToInt32(item.EndPoint) == node.Id)
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }


        public static List<GraphNodeInfoEx> ShortestPathEx(NodeInfo_old fromNode, NodeInfo_old toNode)
        {
            //Initializer();
            Coordinate fromCoor = fromNode.Location;
            Coordinate toCoor = toNode.Location;

            GraphNodeServiceEx nodeService = new GraphNodeServiceEx();
            GraphNodeInfoEx nodeStart = new GraphNodeInfoEx();
            GraphNodeInfoEx nodeEnd = new GraphNodeInfoEx();

            nodeStart.Id = fromNode.Id;
            nodeStart.Location = fromNode.Location;
            nodeEnd.Id = toNode.Id;
            nodeEnd.Location = toNode.Location;



            nodeStart.Father = null;
            nodeStart.CalculateHeuristic(nodeEnd);
            nodeStart.G = 0;
            nodeStart.F = nodeStart.H + nodeStart.G;

            List<GraphNodeInfoEx> lstPriority = new List<GraphNodeInfoEx>();
            List<GraphNodeInfoEx> lstChosen = new List<GraphNodeInfoEx>();
            lstPriority.Add(nodeStart);

            int count = 0;
            while (lstPriority.Count > 0 && count < 2000)
            {
                count++;
                GraphNodeInfoEx currentNode = new GraphNodeInfoEx(lstPriority[0]);
                lstPriority.RemoveAt(0);
                lstChosen.Add(currentNode);

                if (currentNode.Equals(nodeEnd))
                {
                    return GetShortestPath(lstChosen);
                }

                //Xuong db
                List<GraphNodeInfoEx> lstRelation = GetNodeRelation(currentNode.Id);
                    
                //xuoonsg db
                int IDStreet = IsRelated(currentNode.Id, nodeEnd.Id);

                if (IDStreet != 0)
                {
                    lstRelation.Add(nodeEnd);
                    nodeEnd.IDStreet = IDStreet;
                }

                foreach (GraphNodeInfoEx node in lstRelation)
                {
                    //Them
                    //if (node.Equals(nodeEnd))
                    //{
                    //    lstChosen.Add(node);
                    //    return GetShortestPath(lstChosen);
                    //}
                    //End Them

                    node.CalculateHeuristic(nodeEnd);
                    node.G = currentNode.G + (float)Coordinate.Distance(currentNode.Location, node.Location);
                    node.F = node.H + node.G;
                    node.Father = currentNode;

                    if (!lstPriority.Contains(node) && !lstChosen.Contains(node))
                    {
                        lstPriority.Add(node);
                    }

                    if (lstPriority.Contains(node) && node.F < lstPriority[lstPriority.IndexOf(node)].F)
                    {
                        lstPriority.Remove(node);
                        lstPriority.Add(node);
                    }

                    if (lstChosen.Contains(node) && node.F < lstChosen[lstChosen.IndexOf(node)].F)
                    {
                        lstChosen.Remove(node);
                        lstChosen.Add(node);
                    }
                }
                lstPriority.Sort();
            }
            return null;
            
        }

        private static int IsRelated(int idStart, int idEnd)
        {
            foreach (var item in segmentInfoList.SegmentInfoList)
            {
                if ((item.StartPoint.Id == idStart && item.EndPoint.Id == idEnd) ||
                    (item.StartPoint.Id == idEnd && item.EndPoint.Id == idStart))
                {
                    return 1;
                }

            }
            return 0;
            //throw new NotImplementedException();
        }

        public static List<GraphNodeInfoEx> ShortestPathEx2(NodeInfo_old fromNode, NodeInfo_old toNode)
        {
            Coordinate fromCoor = fromNode.Location;
            Coordinate toCoor = toNode.Location;

            GraphNodeServiceEx nodeService = new GraphNodeServiceEx();
            GraphNodeInfoEx nodeStart = new GraphNodeInfoEx();
            GraphNodeInfoEx nodeEnd = new GraphNodeInfoEx();

            nodeStart.Id = fromNode.Id;
            nodeStart.Location = fromNode.Location;
            nodeEnd.Id = toNode.Id;
            nodeEnd.Location = toNode.Location;

            nodeStart.Father = null;

            

            List<GraphNodeInfoEx> openList = new List<GraphNodeInfoEx>();
            List<GraphNodeInfoEx> chooseList = new List<GraphNodeInfoEx>();

            openList.Add(nodeStart);
            int count = 0;
            while (openList.Count > 0 && count < 1000)
            {
                count++;

                GraphNodeInfoEx currentNode = new GraphNodeInfoEx(openList[0]);
                chooseList.Add(currentNode);
                openList.RemoveAt(0);
                if (currentNode.Equals(nodeEnd))
                {
                    return GetShortestPath(chooseList);
                }

                //Xac dinh node se di tien
                //Track routing (x1-xe)*(x2-xe) > 0 (y1-ye)*(y2-ye) > 0
                GraphNodeInfoEx nextNode = defineNextNode(currentNode, nodeEnd);
                nextNode.Father = currentNode;
                //chuyen routing, quay lai xac dinh node se di dau tien
                openList.Add(nextNode);
                

            }

            return null;
        }

        private static GraphNodeInfoEx defineNextNode(GraphNodeInfoEx currentNode, GraphNodeInfoEx nodeEnd)
        {
            GraphNodeInfoEx nextNode = new GraphNodeInfoEx();
            //Xac dinh node se di dau tien
            GraphNodeServiceEx nodeService = new GraphNodeServiceEx();
            List<GraphNodeInfoEx> relateWithCurrentNode = nodeService.GetListNodeRelate(currentNode.Location);

            foreach (var node in relateWithCurrentNode)
            {
                node.CalculateHeuristic(nodeEnd);
                node.G = currentNode.G + (float)Coordinate.Distance(currentNode.Location, node.Location);
                node.F = node.H + node.G;
            }

            relateWithCurrentNode.Sort();
            nextNode = relateWithCurrentNode[0];

            //Track routing (x1-xe)*(x2-xe) > 0 (y1-ye)*(y2-ye) > 0
            //int streetID = nodeService.IsRelated(currentNode.Location, nextNode.Location);

            return nextNode;
        }

        private static List<GraphNodeInfoEx> GetShortestPath(List<GraphNodeInfoEx> closedList)
        {
            List<GraphNodeInfoEx> result = new List<GraphNodeInfoEx>();
            int lastIndex = closedList.Count - 1;
            GraphNodeInfoEx end = closedList[lastIndex];
            GraphNodeInfoEx start = closedList[0];
            result.Add(end);
            GraphNodeInfoEx node = end;
            while (!node.Equals(start))
            {
                node = node.Father;
                result.Add(node);
            }
            result.Reverse();
            return result;
        }

        public static List<GraphNodeInfoEx> ShortestPath(Coordinate fromCoo, Coordinate toCoo)
        {
            //Xac dinh 2 node gan nhat
            Initializer();
            NodeInfo_old startNode = new NodeInfo_old();
            NodeInfo_old endNode = new NodeInfo_old();
            double min = 9999;
            double min2 = 9999;
            foreach (var item in nodeInfoList.NodeInfoList) 
            {
                double temp = Coordinate.Distance(fromCoo, item.Location);
                if (temp < min)
                {
                    min = temp;
                    startNode = item;
                }

                double temp2 = Coordinate.Distance(item.Location,toCoo);
                if (temp2 < min2)
                {
                    min2 = temp2;
                    endNode = item;
                }
            }

            
            //Ex
            return ShortestPathEx(startNode, endNode);
        }


        //New update
        public static List<DirectionDTO> ShortestPaths(Coordinate fromCoo, Coordinate toCoo)
        {
            Initializer();
            NodeInfo_old startNode = new NodeInfo_old();
            NodeInfo_old endNode = new NodeInfo_old();
            double min = 9999;
            double min2 = 9999;
            foreach (NodeInfo_old item in nodeInfoList.NodeInfoList)
            {
                double temp = Coordinate.Distance(fromCoo, item.Location);
                if (temp < min)
                {
                    min = temp;
                    startNode = item;
                }

                double temp2 = Coordinate.Distance(item.Location, toCoo);
                if (temp2 < min2)
                {
                    min2 = temp2;
                    endNode = item;
                }
            }

            List<DirectionDTO> results = new List<DirectionDTO>();

            DirectionDTO minPath = new DirectionDTO();
            List<GraphNodeInfoEx> tempList = ShortestPathEx(startNode, endNode);
            if (tempList != null)
            {
                minPath.Path = ConvertToDirecTion(tempList);
                results.Add(minPath);

            }
            else
            {
                return null;
            }

            int maxNumber = 0;
            List<int> indexMainnode = GetMainNode(tempList);

            while (results.Count < 5 && indexMainnode.Count >= 2)
            {
                maxNumber++;
                int firstIndex = indexMainnode[0];
                int seconIndex = indexMainnode[indexMainnode.Count - 1];
                indexMainnode.RemoveAt(0);
                indexMainnode.RemoveAt(indexMainnode.Count - 1);

                ListNodeInfo tempNodeList = new ListNodeInfo();
                foreach (NodeInfo_old item in nodeInfoList.NodeInfoList)
                {
                    tempNodeList.addNode(item);
                }

                for (int i = firstIndex; i < seconIndex; i++)
                {
                    //NodeInfo_old tempNode =minPath.Path[i];
                    NodeInfo_old tempNode = new NodeInfo_old();
                    tempNode.Id = minPath.Path[i].Id;
                    tempNode.Location = minPath.Path[i].Location;
                    for (int j = 0; j < nodeInfoList.NodeInfoList.Count; j++)
                    {
                        if (nodeInfoList.NodeInfoList[j].Id == tempNode.Id)
                        {
                            nodeInfoList.NodeInfoList.RemoveAt(j);
                            break;
                        }
                    }
                }

                DirectionDTO tempPath = new DirectionDTO();
                List<GraphNodeInfoEx> tempList1 = ShortestPathEx(startNode, endNode);
                if (tempList1 != null && !IsDuplicate(tempList1, results[results.Count - 1]) )
                    
                    {
                        tempPath.Path = ConvertToDirecTion(tempList1);
                        results.Add(tempPath);
                    }

                //Restore nodeInfoList
                nodeInfoList.NodeInfoList.Clear();
                foreach (NodeInfo_old item in tempNodeList.NodeInfoList)
                {
                    nodeInfoList.addNode(item);
                }
                //

            }

            for (int i = 0; i < results.Count; i++)
            {
                //Xac dinh huong
                SetDirection(results[i]);
            }
            return results;
        }

        private static bool IsDuplicate(List<GraphNodeInfoEx> tempPath, DirectionDTO directionDTO)
        {
            if (tempPath.Count == directionDTO.Path.Count)
            {
                for (int i = 0; i < tempPath.Count;i++ )
                {
                    if (tempPath[i].Id != directionDTO.Path[i].Id)
                    {
                        return false;
                    }
                }

                return true;
            }
            else
            {
                return false;
            }
        }

        private static void SetDirection(DirectionDTO directionDTO)
        {
            for (int i = 1; i < directionDTO.Path.Count - 1; i++)
            {
                int id1 = directionDTO.Path[i].Id;
                int id2 = directionDTO.Path[i + 1].Id;

                if (id1 == id2 + 1 || id1 == id2 - 1 && i != 1)
                {
                    string streetName = SetStreet(directionDTO.Path[i], directionDTO.Path[i + 1]);
                    directionDTO.Path[i].NextStreet = streetName;
                    
                }
                else
                {
                    Coordinate coo1 = directionDTO.Path[i - 1].Location;
                    Coordinate coo2 = directionDTO.Path[i].Location;
                    Coordinate coo3 = directionDTO.Path[i + 1].Location;

                    double a = coo2.Longitude - coo1.Longitude;
                    double b = coo2.Latitude - coo1.Latitude;
                    double c = coo3.Longitude - coo2.Longitude;
                    double d = coo3.Latitude - coo2.Latitude;

                    double cos = (a * c + b * d) / (Math.Sqrt(a * a + b * b) * Math.Sqrt(c * c + d * d));
                    double angle = Math.Acos(cos);
                    if (angle < 3 * Math.PI / 4)
                    {
                        double unknown = -b * (c) + a * d;
                        if (unknown > 0)
                        {
                            directionDTO.Path[i].DirectionGuide = 1;
                            string streetName = SetStreet(directionDTO.Path[i], directionDTO.Path[i + 1]);
                            directionDTO.Path[i].NextStreet = streetName;

                        }
                        else
                        {
                            directionDTO.Path[i].DirectionGuide = -1;
                            string streetName = SetStreet(directionDTO.Path[i], directionDTO.Path[i + 1]);
                            directionDTO.Path[i].NextStreet = streetName;

                        }

                    }

                }
            }
        }

        private static string SetStreet(DirectionNode node1, DirectionNode node2)
        {
            int streetID = 0;
            foreach (SegmentInfo item in segmentInfoList.SegmentInfoList)
            {
                if ((item.StartPoint.Id == node1.Id && item.EndPoint.Id == node2.Id) ||
                    (item.StartPoint.Id == node2.Id && item.EndPoint.Id == node1.Id))
                {
                    streetID = item.IdStreet;
                    break;
                }
            }

            string result = DirectionDAO.GetStreetName(streetID);
            return result;
        }

        private static List<int> GetMainNode(List<GraphNodeInfoEx> tList)
        {
            List<int> result = new List<int>();
            for (int i = 0; i < tList.Count; i++)
            {
                List<GraphNodeInfoEx> lstRelation = GetNodeRelation(tList[i].Id);
                if (lstRelation.Count > 2)
                {
                    result.Add(i);
                }
            }

            return result;
        }


        private static List<DirectionNode> ConvertToDirecTion(List<GraphNodeInfoEx> list)
        {
            List<DirectionNode> result = new List<DirectionNode>();
            foreach (GraphNodeInfoEx item in list)
            {
                DirectionNode temp = new DirectionNode();
                temp.Id = item.Id;
                temp.Location = item.Location;
                result.Add(temp);
            }
            return result;
        }
        

    }
}
